﻿namespace ImageDupFinder
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Text.RegularExpressions;
    using ImageDupFinder.ProgramCore;

    /// <summary>
    /// Manages Filesystem related tasks and thumbnail loading.
    /// </summary>
    public class FileManager
    {
        /// <summary>
        /// list of file system watchers.
        /// </summary>
        private readonly List<FileSystemWatcher> fileWatchers;

        /// <summary>
        /// list of selected files.
        /// </summary>
        private readonly List<string> selectedFiles;

        /// <summary>
        /// thumbnail loader.
        /// </summary>
        private readonly ThumbLoader thumbnailLoader;

        /// <summary>
        /// image manager used by this file manager.
        /// </summary>
        private ImageManager imageManager;

        /// <summary>
        /// supported file types - file pattern.
        /// </summary>
        private string filePattern = "*.jpg,*.jpeg,*.png,*.bmp,*.gif";

        /// <summary>
        /// supported file types - regexp.
        /// </summary>
        private Regex regexpFileExt = new Regex(@"\.(jpg|jpeg|png|bmp|gif)$", RegexOptions.IgnoreCase);

        /// <summary>
        /// Initializes a new instance of the FileManager class using provided image manager.
        /// </summary>
        /// <param name="manager">image manager used by this file manager</param>
        public FileManager(ImageManager manager)
        {
            this.imageManager = manager;
            this.thumbnailLoader = new ThumbLoader();
            this.selectedFiles = new List<string>();
            this.fileWatchers = new List<FileSystemWatcher>();
        }

        /// <summary>
        /// Occurs when thumbnail image is loaded
        /// </summary>
        public event EventHandler<ThumbLoaderItem> ThumbnailImageLoaded
        {
            add { thumbnailLoader.ThumbnailLoadedCallback += value; }
            remove { thumbnailLoader.ThumbnailLoadedCallback -= value; }
        }

        /// <summary>
        /// Occurs when file is removed from disk
        /// </summary>
        public event EventHandler<FileEventArgs> FileRemoved;

        /// <summary>
        /// Occurs when file is succesfuly added
        /// </summary>
        public event EventHandler FilesAdded;

        /// <summary>
        /// Gets file count
        /// </summary>
        public int FileCount
        {
            get
            {
                return imageManager.FileCount;
            }
        }

        /// <summary>
        /// Gets list of selected files
        /// </summary>
        public List<string> SelectedFiles
        {
            get { return selectedFiles; }
        }

        /// <summary>
        /// Adds file to manager
        /// </summary>
        /// <param name="paths">File path.</param>
        public void AddFiles(string[] paths)
        {
            List<string> files = new List<string>();
            foreach (string f in paths)
            {
                if (Directory.Exists(f))
                {
                    // search folder for files
                    foreach (string ext in filePattern.Split(','))
                    {
                        try
                        {
                            files.AddRange(Directory.GetFiles(f, ext, SearchOption.AllDirectories));
                        }
                        catch (IOException ioe)
                        {
                            System.Diagnostics.Debug.Write(String.Format("Error: {0}", ioe.ToString()));
                        }
                        catch (UnauthorizedAccessException uae)
                        {
                            System.Diagnostics.Debug.Write(String.Format("Error: {0}", uae.ToString()));
                        }
                    }

                    AddDirWatcher(f);
                }
                else if (File.Exists(f) && regexpFileExt.IsMatch(f))
                {
                    files.Add(f);
                    AddFileWatcher(f);
                }
            }

            // process files
            int added = imageManager.ProcessFiles(files);

            // raise event
            EventHandler filesAded_tmp = FilesAdded;
            if (added > 0 && filesAded_tmp != null)
            {
                filesAded_tmp(this, new EventArgs());
            }
        }

        /// <summary>
        /// Clears file list
        /// </summary>
        public void Clear()
        {
            imageManager.Clear();
            selectedFiles.Clear();
            foreach (FileSystemWatcher fsw in fileWatchers)
            {
                fsw.Dispose();
            }

            fileWatchers.Clear();
        }

        /// <summary>
        /// Load thumbnail image in asynchronous way
        /// </summary>
        /// <param name="item">Thumbnail loader item to process</param>
        public void LoadThumbnailAsync(ThumbLoaderItem item)
        {
            this.thumbnailLoader.LoadAsync(item);
        }

        /// <summary>
        /// Removes files form manager and disk
        /// </summary>
        /// <param name="files">files to remove</param>
        public void RemoveFiles(string[] files)
        {
            /*// suprass filesystem watchers
            foreach (FileSystemWatcher fsw in fileWatchers)
                fsw.EnableRaisingEvents = false;
            */

            // remove file and raise event
            foreach (string file in files)
            {
                selectedFiles.Remove(file);
                if (System.IO.File.Exists(file))
                {
                    // let file system watcher rise events
                    DeleteToRecycleBin.RecybleBin.SendSilent(file);
                }

                /*//raise event
                 if (imageManager.RemoveFile(file))
                    FileRemoved(this, new FileEventArgs(file));*/
            }

            /*//resume filesystem watchers
            foreach (FileSystemWatcher fsw in fileWatchers)
                fsw.EnableRaisingEvents = true;*/
        }

        /// <summary>
        /// Removes all selected files
        /// </summary>
        public void RemoveSelectedFiles()
        {
            RemoveFiles(this.selectedFiles.ToArray());
        }

        /// <summary>
        /// Creates new file system watcher for specified directory
        /// </summary>
        /// <param name="dir">Directory path</param>
        private void AddDirWatcher(string dir)
        {
            bool removeSubfolders = false;
            List<FileSystemWatcher> toRemove = new List<FileSystemWatcher>();
            foreach (FileSystemWatcher fsw in fileWatchers)
            {
                string path = fsw.Path;

                // test if folder is already watched
                if (path == dir)
                {
                    return;
                }

                // test if parent folder is already watched
                if (path.Length < dir.Length && dir.StartsWith(path))
                {
                    return;
                }

                // test if sub folder is already watched
                if (path.Length > dir.Length && path.StartsWith(dir))
                {
                    if (removeSubfolders)
                    {
                        toRemove.Add(fsw);
                    }
                    else
                    {
                        fsw.Path = dir;
                        removeSubfolders = true;
                    }
                }
            }

            if (removeSubfolders)
            {
                foreach (FileSystemWatcher fsw in toRemove)
                {
                    fileWatchers.Remove(fsw);
                    fsw.Dispose();
                }
            }
            else
            {
                // create new watcher
                FileSystemWatcher fsw = new FileSystemWatcher(dir);

                // increase buffer size to 16kb (default 8kb)
                fsw.InternalBufferSize = 16384;

                // set filters
                fsw.IncludeSubdirectories = true;
                fsw.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastWrite;

                // set events
                fsw.Changed += new FileSystemEventHandler(OnChanged);
                fsw.Deleted += new FileSystemEventHandler(OnChanged);
                fsw.Renamed += new RenamedEventHandler(OnRenamed);
                fsw.EnableRaisingEvents = true;

                // add to list
                fileWatchers.Add(fsw);
            }
        }

        /// <summary>
        /// Creates new file system watcher for specified file
        /// </summary>
        /// <param name="file">File path.</param>
        private void AddFileWatcher(string file)
        {
            try
            {
                AddDirWatcher(Directory.GetParent(file).FullName);
            }
            catch (IOException ioe)
            {
                System.Diagnostics.Debug.Write(String.Format("Error: {0}", ioe.ToString()));
            }
            catch (UnauthorizedAccessException uae)
            {
                System.Diagnostics.Debug.Write(String.Format("Error: {0}", uae.ToString()));
            }
        }

        #region event handlers

        /// <summary>
        /// Handles file changed event raised by filesystem.
        /// </summary>
        /// <param name="source">Sender object</param>
        /// <param name="e">event args</param>
        private void OnChanged(object source, FileSystemEventArgs e)
        {
            if (regexpFileExt.IsMatch(e.Name) && imageManager.RemoveFile(e.FullPath))
                if (FileRemoved != null)
                {   // cross thread invocation
                    object[] args = new object[] { this, new FileEventArgs(e.FullPath) };
                    foreach (Delegate d in FileRemoved.GetInvocationList())
                    {
                        ISynchronizeInvoke isi = d.Target as ISynchronizeInvoke;
                        if (isi != null && isi.InvokeRequired)
                        {
                            isi.BeginInvoke(d, args);
                        }
                        else
                        {
                            d.DynamicInvoke(args);
                        }
                    }
                }
        }

        /// <summary>
        /// Handles file renamed event raised by filesystem.
        /// </summary>
        /// <param name="source">Sender object</param>
        /// <param name="e">Event args</param>
        private void OnRenamed(object source, RenamedEventArgs e)
        {
            if (regexpFileExt.IsMatch(e.OldName) && imageManager.RemoveFile(e.OldFullPath))
                if (FileRemoved != null)
                {   // cross thread invocation
                    object[] args = new object[] { this, new FileEventArgs(e.OldFullPath) };
                    foreach (Delegate d in FileRemoved.GetInvocationList())
                    {
                        ISynchronizeInvoke isi = d.Target as ISynchronizeInvoke;
                        if (isi != null && isi.InvokeRequired)
                        {
                            isi.BeginInvoke(d, args);
                        }
                        else
                        {
                            d.DynamicInvoke(args);
                        }
                    }
                }
        }
        #endregion
    }

    /// <summary>
    /// Class containing file event data
    /// </summary>
    public class FileEventArgs : EventArgs
    {
        /// <summary>
        /// File path.
        /// </summary>
        private string file;

        /// <summary>
        /// Initializes a new instance of the FileEventArgs class.
        /// </summary>
        /// <param name="file">File path.</param>
        public FileEventArgs(string file)
        {
            this.file = file;
        }

        /// <summary>
        /// Gets or sets File path.
        /// </summary>
        public string File
        {
            get { return file; }
            set { file = value; }
        }
    }
}
